###########################################################################
#
# File:         doctestmod.py
# Date:         16-Oct-2007
# Author:       Hugh Secker-Walker
# Description:  Tool for running doctest on a module
#
# This file is part of Onyx   http://onyxtools.sourceforge.net
#
# Copyright 2007 - 2009 The Johns Hopkins University
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#   http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.  See the License for the specific language governing
# permissions and limitations under the License.
#
###########################################################################

"""
Module for running of doctest on Onyx modules.

Due to recursive issues it is impractical for this module to doctest
itself.    See onyx.__init__._test_doctest_functions() for tests.
"""

import sys, os
import onyx
from onyx.util.duration import time_usec_sec_str

# a string tag intended to make it easier to search for doctestmod
# results in log files
DOCTESTMODTAG = 'doctestmod'

def make_doctest_message(fullfile, num_bad, num_test, elapsed_time_sec):
    """
    Create a well-defined error message for summarizing doctests.

    See onyx.__init__._test_doctest_functions() for tests which will have to be
    updated if the formatting of the doctest msg is changed.
    """
    _, file = os.path.split(fullfile)
    passfail = "pass" if num_bad == 0 else ("*** fail *** : file %r" % (fullfile,))
    num_ok = num_test - num_bad
    elapsed_time_usec_str, elapsed_time_sec_str = time_usec_sec_str(elapsed_time_sec)
    msg = "%s : %s : total %r  ok %r  bad %r : %s : elapsed time %s usec (%s seconds) \n" % (DOCTESTMODTAG, file, num_test, num_ok, num_bad, passfail, elapsed_time_usec_str, elapsed_time_sec_str)
    return msg

import re
matcher = re.compile(r'^(\w+) : ([^ :]+) : total\s+(\d+)\s+ok\s+(\d+)\s+bad\s+(\d+)\s* : (.*)$').match
def parse_doctest_message(msg):
    """
    Attempt to parse a message generated by doctestmod().

    Returns None if the parse failed.  Returns a tuple (tag,
    module_name, total, ok, bad, passfail) where tag should be the
    value of DOCTESTMOD; module_name is the module's name; total, ok,
    and bad are integers summarizing the test results; and passfail is
    a string with the pass or fail message

    See onyx.__init__._test_doctest_functions() for tests.
    """
    match = matcher(msg.strip())
    if match is None:
       return None
    tag, module_name, total, ok, bad, passfail = match.groups()
    return tag, module_name, int(total), int(ok), int(bad), passfail

def doctestmod(module=None):
    """
    A doctest wrapper that runs doctest.testmod() on the module.  It
    returns a tuple: (num_ok, num_bad, string with a summary of the
    testing results).  The one-line string is prefixed with the value
    of DOCTESTMODTAG and terminated with a newline.

    Optional 'module' argument is the module to test.  If not given,
    or None, the module called '__main__' will be used.  

    This function is particularly difficult to test other than
    functionally in the working system.
    """
    if module is None:
        module = sys.modules.get('__main__')    

    import doctest
    import time
    start = time.time()
    num_bad, num_test = doctest.testmod(module, report=False, verbose=False)
    elapsed_time = time.time() - start
    msg = make_doctest_message(module.__file__, num_bad, num_test, elapsed_time)
    return num_test - num_bad, num_bad, msg


if __name__ == '__main__':

    # Note: it appears to be impractical to have a direct doctest of
    # this module due to the recursive use of doctest that such a test
    # would entail, so we fake the doctestmod results
    sys.stdout.write(make_doctest_message('doctestmod(fake)', 0, 1, 0.1))

    # See onyx.__init__._test_doctest_functions() for tests of some functions.
